Coverage Report

Created: 2026-02-12 08:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
D:\a\csshw\csshw\src\utils\windows.rs
Line
Count
Source
1
//! Windows API abstraction layer for console and system operations.
2
//!
3
//! This module provides a trait-based abstraction over Windows APIs to enable
4
//! mocking in tests and centralize Windows-specific functionality.
5
6
#![deny(clippy::implicit_return)]
7
#![allow(
8
    clippy::needless_return,
9
    clippy::doc_overindented_list_items,
10
    rustdoc::private_intra_doc_links
11
)]
12
13
use log::error;
14
use std::ffi::OsString;
15
use std::os::windows::ffi::OsStrExt;
16
use std::{mem, ptr};
17
18
use windows::core::{HSTRING, PCWSTR};
19
use windows::Win32::Foundation::{BOOL, COLORREF, FALSE, HANDLE, HWND, LPARAM, TRUE};
20
use windows::Win32::Graphics::Dwm::{DwmSetWindowAttribute, DWMWA_BORDER_COLOR};
21
use windows::Win32::System::Com::{CoCreateInstance, CLSCTX_ALL};
22
use windows::Win32::System::Console::{
23
    FillConsoleOutputAttribute, GetConsoleProcessList, GetConsoleScreenBufferInfo,
24
    GetConsoleWindow, GetStdHandle, ReadConsoleInputW, SetConsoleTextAttribute,
25
    CONSOLE_CHARACTER_ATTRIBUTES, CONSOLE_SCREEN_BUFFER_INFO, COORD, INPUT_RECORD, INPUT_RECORD_0,
26
    STD_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE,
27
};
28
use windows::Win32::System::Console::{GetConsoleMode, SetConsoleMode, CONSOLE_MODE};
29
use windows::Win32::System::Console::{
30
    ScrollConsoleScreenBufferW, SetConsoleCursorPosition, CHAR_INFO, KEY_EVENT as KEY_EVENT_U32,
31
    SMALL_RECT,
32
};
33
use windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS;
34
use windows::Win32::System::Threading::{
35
    CreateProcessW, CREATE_NEW_CONSOLE, PROCESS_INFORMATION, STARTUPINFOW,
36
};
37
use windows::Win32::System::Threading::{GetExitCodeProcess, OpenProcess};
38
use windows::Win32::UI::Accessibility::{CUIAutomation, IUIAutomation};
39
use windows::Win32::UI::WindowsAndMessaging::{
40
    EnumWindows, GetWindowTextW, GetWindowThreadProcessId, MoveWindow, SetWindowTextW,
41
    SYSTEM_METRICS_INDEX,
42
};
43
use windows::Win32::UI::WindowsAndMessaging::{
44
    GetForegroundWindow, GetWindowPlacement, SetForegroundWindow, ShowWindow, SHOW_WINDOW_CMD,
45
    WINDOWPLACEMENT,
46
};
47
48
#[cfg(test)]
49
use mockall::automock;
50
51
use super::constants::MAX_WINDOW_TITLE_LENGTH;
52
53
/// Trait for Windows API operations to enable mocking in tests.
54
///
55
/// This trait abstracts Windows API calls to allow for unit testing without
56
/// actual system interaction. All console and system operations should go
57
/// through this trait.
58
#[cfg_attr(test, automock)]
59
pub trait WindowsApi: Send + Sync {
60
    /// Sets the console window title.
61
    ///
62
    /// # Arguments
63
    ///
64
    /// * `title` - The string to be set as window title
65
    ///
66
    /// # Returns
67
    ///
68
    /// Result indicating success or failure of the operation
69
    fn set_console_title(&self, title: &str) -> windows::core::Result<()>;
70
71
    /// Gets the console window title as UTF-16 buffer.
72
    ///
73
    /// # Arguments
74
    ///
75
    /// * `buffer` - Mutable buffer to store the UTF-16 encoded title
76
    ///
77
    /// # Returns
78
    ///
79
    /// Number of characters copied to the buffer
80
    fn get_console_title(&self, buffer: &mut [u16]) -> i32;
81
82
    /// Gets OS version string.
83
    ///
84
    /// # Returns
85
    ///
86
    /// String representation of the OS version
87
    fn get_os_version(&self) -> String;
88
89
    /// Arranges the console window position and size.
90
    ///
91
    /// # Arguments
92
    ///
93
    /// * `x` - The x coordinate to move the window to
94
    /// * `y` - The y coordinate to move the window to
95
    /// * `width` - The width in pixels to resize the window to
96
    /// * `height` - The height in pixels to resize the window to
97
    ///
98
    /// # Returns
99
    ///
100
    /// Result indicating success or failure of the operation
101
    fn arrange_console(&self, x: i32, y: i32, width: i32, height: i32)
102
        -> windows::core::Result<()>;
103
104
    /// Sets console text attribute.
105
    ///
106
    /// # Arguments
107
    ///
108
    /// * `attributes` - Console character attributes to set
109
    ///
110
    /// # Returns
111
    ///
112
    /// Result indicating success or failure of the operation
113
    fn set_console_text_attribute(
114
        &self,
115
        attributes: CONSOLE_CHARACTER_ATTRIBUTES,
116
    ) -> windows::core::Result<()>;
117
118
    /// Gets console screen buffer info.
119
    ///
120
    /// # Returns
121
    ///
122
    /// Console screen buffer information or error
123
    fn get_console_screen_buffer_info(&self) -> windows::core::Result<CONSOLE_SCREEN_BUFFER_INFO>;
124
125
    /// Fills console output with specified attribute.
126
    ///
127
    /// # Arguments
128
    ///
129
    /// * `attribute` - Attribute to fill with
130
    /// * `length` - Number of characters to fill
131
    /// * `coord` - Starting coordinate
132
    ///
133
    /// # Returns
134
    ///
135
    /// Number of characters actually filled or error
136
    fn fill_console_output_attribute(
137
        &self,
138
        attribute: u16,
139
        length: u32,
140
        coord: COORD,
141
    ) -> windows::core::Result<u32>;
142
143
    /// Scrolls console screen buffer.
144
    ///
145
    /// # Arguments
146
    ///
147
    /// * `scroll_rect` - Rectangle to scroll
148
    /// * `scroll_target` - Target coordinate for scrolling
149
    /// * `fill_char` - Character to fill empty space with
150
    ///
151
    /// # Returns
152
    ///
153
    /// Result indicating success or failure of the operation
154
    fn scroll_console_screen_buffer(
155
        &self,
156
        scroll_rect: SMALL_RECT,
157
        scroll_target: COORD,
158
        fill_char: CHAR_INFO,
159
    ) -> windows::core::Result<()>;
160
161
    /// Sets console cursor position.
162
    ///
163
    /// # Arguments
164
    ///
165
    /// * `position` - New cursor position
166
    ///
167
    /// # Returns
168
    ///
169
    /// Result indicating success or failure of the operation
170
    fn set_console_cursor_position(&self, position: COORD) -> windows::core::Result<()>;
171
172
    /// Gets standard handle.
173
    ///
174
    /// # Arguments
175
    ///
176
    /// * `handle_type` - Type of standard handle to retrieve
177
    ///
178
    /// # Returns
179
    ///
180
    /// Handle to the requested standard device or error
181
    fn get_std_handle(&self, handle_type: STD_HANDLE) -> windows::core::Result<HANDLE>;
182
183
    /// Reads console input.
184
    ///
185
    /// # Arguments
186
    ///
187
    /// * `buffer` - Buffer to store input records
188
    ///
189
    /// # Returns
190
    ///
191
    /// Number of records read or error
192
    fn read_console_input(&self, buffer: &mut [INPUT_RECORD]) -> windows::core::Result<u32>;
193
194
    /// Sets DWM window attribute for border color.
195
    ///
196
    /// # Arguments
197
    ///
198
    /// * `color` - Color to set as border color
199
    ///
200
    /// # Returns
201
    ///
202
    /// Result indicating success or failure of the operation
203
    fn set_console_border_color(&self, color: &COLORREF) -> windows::core::Result<()>;
204
205
    /// Writes input records to the console input buffer.
206
    ///
207
    /// # Arguments
208
    ///
209
    /// * `buffer` - Input records to write
210
    /// * `number_written` - Mutable reference to store number of records written
211
    ///
212
    /// # Returns
213
    ///
214
    /// Result indicating success or failure of the operation
215
    fn write_console_input(
216
        &self,
217
        buffer: &[INPUT_RECORD],
218
        number_written: &mut u32,
219
    ) -> windows::core::Result<()>;
220
221
    /// Gets the last Windows error code.
222
    ///
223
    /// # Returns
224
    ///
225
    /// The last error code from Windows API
226
    fn get_last_error(&self) -> u32;
227
228
    /// Generates a console control event.
229
    ///
230
    /// # Arguments
231
    ///
232
    /// * `ctrl_event` - Control event type to generate
233
    /// * `process_group_id` - Process group ID to send event to
234
    ///
235
    /// # Returns
236
    ///
237
    /// Result indicating success or failure of the operation
238
    fn generate_console_ctrl_event(
239
        &self,
240
        ctrl_event: u32,
241
        process_group_id: u32,
242
    ) -> windows::core::Result<()>;
243
244
    /// Get standard output handle
245
    ///
246
    /// # Returns
247
    ///
248
    /// Handle to standard output or error
249
    fn get_stdout_handle(&self) -> windows::core::Result<HANDLE>;
250
251
    /// Get console screen buffer information
252
    ///
253
    /// # Arguments
254
    ///
255
    /// * `handle` - Handle to console screen buffer
256
    ///
257
    /// # Returns
258
    ///
259
    /// Console screen buffer information or error
260
    fn get_console_attached_process_count(&self) -> u32;
261
262
    /// Create a new process
263
    ///
264
    /// # Arguments
265
    ///
266
    /// * `application` - Application name including file extension
267
    /// * `args` - List of arguments to the application
268
    ///
269
    /// # Returns
270
    ///
271
    /// Process information if successful, None otherwise
272
1
    fn create_process_with_args(
273
1
        &self,
274
1
        application: &str,
275
1
        args: Vec<String>,
276
1
    ) -> Option<windows::Win32::System::Threading::PROCESS_INFORMATION> {
277
1
        let command_line = build_command_line(application, &args);
278
1
        let mut startupinfo = STARTUPINFOW {
279
1
            cb: mem::size_of::<STARTUPINFOW>() as u32,
280
1
            ..Default::default()
281
1
        };
282
1
        let mut process_information = PROCESS_INFORMATION::default();
283
1
        let mut cmd_line = command_line;
284
1
        let command_line_ptr = windows::core::PWSTR(cmd_line.as_mut_ptr());
285
286
1
        match self.create_process_raw(
287
1
            application,
288
1
            command_line_ptr,
289
1
            &mut startupinfo,
290
1
            &mut process_information,
291
1
        ) {
292
1
            Ok(()) => return Some(process_information),
293
0
            Err(_) => return None,
294
        }
295
1
    }
296
297
    /// Low-level process creation API call
298
    ///
299
    /// # Arguments
300
    ///
301
    /// * `application` - Application name
302
    /// * `command_line` - Command line string as PWSTR
303
    /// * `startup_info` - Startup information structure
304
    /// * `process_info` - Process information structure to fill
305
    ///
306
    /// # Returns
307
    ///
308
    /// Result indicating success or failure of the operation
309
    fn create_process_raw(
310
        &self,
311
        application: &str,
312
        command_line: windows::core::PWSTR,
313
        startup_info: &mut windows::Win32::System::Threading::STARTUPINFOW,
314
        process_info: &mut windows::Win32::System::Threading::PROCESS_INFORMATION,
315
    ) -> windows::core::Result<()>;
316
317
    /// Get window handle for process ID
318
    ///
319
    /// # Arguments
320
    ///
321
    /// * `process_id` - Process ID to find window for
322
    ///
323
    /// # Returns
324
    ///
325
    /// Window handle for the process
326
    fn get_window_handle_for_process(&self, process_id: u32) -> HWND;
327
328
    /// Gets the console window handle.
329
    ///
330
    /// # Returns
331
    ///
332
    /// Handle to the console window
333
    fn get_console_window(&self) -> HWND;
334
335
    /// Gets the foreground window handle.
336
    ///
337
    /// # Returns
338
    ///
339
    /// Handle to the foreground window
340
    fn get_foreground_window(&self) -> HWND;
341
342
    /// Sets the foreground window.
343
    ///
344
    /// # Arguments
345
    ///
346
    /// * `hwnd` - Handle to the window to set as foreground
347
    ///
348
    /// # Returns
349
    ///
350
    /// Result indicating success or failure of the operation
351
    fn set_foreground_window(&self, hwnd: HWND) -> windows::core::Result<()>;
352
353
    /// Gets console mode for the specified handle.
354
    ///
355
    /// # Arguments
356
    ///
357
    /// * `handle` - Handle to the console input buffer
358
    ///
359
    /// # Returns
360
    ///
361
    /// Console mode or error
362
    fn get_console_mode(&self, handle: HANDLE) -> windows::core::Result<CONSOLE_MODE>;
363
364
    /// Sets console mode for the specified handle.
365
    ///
366
    /// # Arguments
367
    ///
368
    /// * `handle` - Handle to the console input buffer
369
    /// * `mode` - Console mode to set
370
    ///
371
    /// # Returns
372
    ///
373
    /// Result indicating success or failure of the operation
374
    fn set_console_mode(&self, handle: HANDLE, mode: CONSOLE_MODE) -> windows::core::Result<()>;
375
376
    /// Gets the exit code of the specified process.
377
    ///
378
    /// # Arguments
379
    ///
380
    /// * `handle` - Handle to the process
381
    ///
382
    /// # Returns
383
    ///
384
    /// Exit code or error
385
    fn get_exit_code(&self, handle: HANDLE) -> windows::core::Result<u32>;
386
387
    /// Moves and resizes a window.
388
    ///
389
    /// # Arguments
390
    ///
391
    /// * `hwnd` - Handle to the window
392
    /// * `x` - New x position
393
    /// * `y` - New y position
394
    /// * `width` - New width
395
    /// * `height` - New height
396
    /// * `repaint` - Whether to repaint the window
397
    ///
398
    /// # Returns
399
    ///
400
    /// Result indicating success or failure of the operation
401
    fn move_window(
402
        &self,
403
        hwnd: HWND,
404
        x: i32,
405
        y: i32,
406
        width: i32,
407
        height: i32,
408
        repaint: bool,
409
    ) -> windows::core::Result<()>;
410
411
    /// Gets window placement information.
412
    ///
413
    /// # Arguments
414
    ///
415
    /// * `hwnd` - Handle to the window
416
    ///
417
    /// # Returns
418
    ///
419
    /// Window placement information or error
420
    fn get_window_placement(&self, hwnd: HWND) -> windows::core::Result<WINDOWPLACEMENT>;
421
422
    /// Shows a window in the specified state.
423
    ///
424
    /// # Arguments
425
    ///
426
    /// * `hwnd` - Handle to the window
427
    /// * `cmd_show` - Show command
428
    ///
429
    /// # Returns
430
    ///
431
    /// Result indicating success or failure of the operation
432
    fn show_window(&self, hwnd: HWND, cmd_show: SHOW_WINDOW_CMD) -> windows::core::Result<bool>;
433
434
    /// Focuses a window using UI Automation.
435
    ///
436
    /// # Arguments
437
    ///
438
    /// * `hwnd` - Handle to the window to focus
439
    ///
440
    /// # Returns
441
    ///
442
    /// Result indicating success or failure of the operation
443
    fn focus_window_with_automation(&self, hwnd: HWND) -> windows::core::Result<()>;
444
445
    /// Checks if a window handle is valid.
446
    ///
447
    /// # Arguments
448
    ///
449
    /// * `hwnd` - Handle to the window to check
450
    ///
451
    /// # Returns
452
    ///
453
    /// True if the window is valid, false otherwise
454
    fn is_window(&self, hwnd: HWND) -> bool;
455
456
    /// Opens a process with the specified access rights.
457
    ///
458
    /// # Arguments
459
    ///
460
    /// * `access` - Access rights for the process handle
461
    /// * `inherit` - Whether the handle can be inherited
462
    /// * `process_id` - Process ID to open
463
    ///
464
    /// # Returns
465
    ///
466
    /// Process handle or error
467
    fn open_process(
468
        &self,
469
        access: u32,
470
        inherit: bool,
471
        process_id: u32,
472
    ) -> windows::core::Result<HANDLE>;
473
474
    /// Initializes the COM library for use by the calling thread.
475
    ///
476
    /// # Arguments
477
    ///
478
    /// * `coinit` - Initialization options for the COM library
479
    ///
480
    /// # Returns
481
    ///
482
    /// Result indicating success or failure of the operation
483
    fn initialize_com_library(
484
        &self,
485
        coinit: windows::Win32::System::Com::COINIT,
486
    ) -> windows::core::Result<()>;
487
488
    /// Gets system metrics information.
489
    ///
490
    /// # Arguments
491
    ///
492
    /// * `index` - System metric index to retrieve
493
    ///
494
    /// # Returns
495
    ///
496
    /// The requested system metric value
497
    fn get_system_metrics(&self, index: SYSTEM_METRICS_INDEX) -> i32;
498
499
    /// Sets the process DPI awareness.
500
    ///
501
    /// # Arguments
502
    ///
503
    /// * `value` - DPI awareness value to set
504
    ///
505
    /// # Returns
506
    ///
507
    /// Result indicating success or failure of the operation
508
    fn set_process_dpi_awareness(
509
        &self,
510
        value: windows::Win32::UI::HiDpi::PROCESS_DPI_AWARENESS,
511
    ) -> windows::core::Result<()>;
512
}
513
514
#[cfg(test)]
515
impl Clone for MockWindowsApi {
516
0
    fn clone(&self) -> Self {
517
0
        return MockWindowsApi::new();
518
0
    }
519
}
520
521
/// Default implementation of WindowsApi that calls actual Windows APIs.
522
///
523
/// This implementation provides direct access to Windows system APIs and should
524
/// be used in production code. For testing, use the MockWindowsApi instead.
525
#[derive(Clone)]
526
pub struct DefaultWindowsApi;
527
528
impl WindowsApi for DefaultWindowsApi {
529
0
    fn set_console_title(&self, title: &str) -> windows::core::Result<()> {
530
0
        return unsafe { SetWindowTextW(GetConsoleWindow(), &HSTRING::from(title)) };
531
0
    }
532
533
0
    fn get_console_title(&self, buffer: &mut [u16]) -> i32 {
534
0
        return unsafe { GetWindowTextW(GetConsoleWindow(), buffer) };
535
0
    }
536
537
0
    fn get_os_version(&self) -> String {
538
0
        return os_info::get().version().to_string();
539
0
    }
540
541
0
    fn arrange_console(
542
0
        &self,
543
0
        x: i32,
544
0
        y: i32,
545
0
        width: i32,
546
0
        height: i32,
547
0
    ) -> windows::core::Result<()> {
548
0
        return unsafe { MoveWindow(GetConsoleWindow(), x, y, width, height, true) };
549
0
    }
550
551
0
    fn set_console_text_attribute(
552
0
        &self,
553
0
        attributes: CONSOLE_CHARACTER_ATTRIBUTES,
554
0
    ) -> windows::core::Result<()> {
555
0
        return unsafe { SetConsoleTextAttribute(self.get_stdout_handle()?, attributes) };
556
0
    }
557
558
0
    fn get_console_screen_buffer_info(&self) -> windows::core::Result<CONSOLE_SCREEN_BUFFER_INFO> {
559
0
        let mut buffer_info = CONSOLE_SCREEN_BUFFER_INFO::default();
560
0
        unsafe { GetConsoleScreenBufferInfo(self.get_stdout_handle()?, &mut buffer_info)? };
561
0
        return Ok(buffer_info);
562
0
    }
563
564
0
    fn fill_console_output_attribute(
565
0
        &self,
566
0
        attribute: u16,
567
0
        length: u32,
568
0
        coord: COORD,
569
0
    ) -> windows::core::Result<u32> {
570
0
        let mut number_written = 0u32;
571
        unsafe {
572
0
            FillConsoleOutputAttribute(
573
0
                self.get_stdout_handle()?,
574
0
                attribute,
575
0
                length,
576
0
                coord,
577
0
                &mut number_written,
578
0
            )?
579
        };
580
0
        return Ok(number_written);
581
0
    }
582
583
0
    fn scroll_console_screen_buffer(
584
0
        &self,
585
0
        scroll_rect: SMALL_RECT,
586
0
        scroll_target: COORD,
587
0
        fill_char: CHAR_INFO,
588
0
    ) -> windows::core::Result<()> {
589
        return unsafe {
590
0
            ScrollConsoleScreenBufferW(
591
0
                self.get_stdout_handle()?,
592
0
                &scroll_rect,
593
0
                None,
594
0
                scroll_target,
595
0
                &fill_char,
596
            )
597
        };
598
0
    }
599
600
0
    fn set_console_cursor_position(&self, position: COORD) -> windows::core::Result<()> {
601
0
        return unsafe { SetConsoleCursorPosition(self.get_stdout_handle()?, position) };
602
0
    }
603
604
0
    fn get_std_handle(&self, handle_type: STD_HANDLE) -> windows::core::Result<HANDLE> {
605
0
        return unsafe { GetStdHandle(handle_type) };
606
0
    }
607
608
0
    fn read_console_input(&self, buffer: &mut [INPUT_RECORD]) -> windows::core::Result<u32> {
609
0
        let mut number_read = 0u32;
610
        unsafe {
611
0
            ReadConsoleInputW(
612
0
                self.get_std_handle(STD_INPUT_HANDLE)?,
613
0
                buffer,
614
0
                &mut number_read,
615
0
            )?
616
        };
617
0
        return Ok(number_read);
618
0
    }
619
620
0
    fn set_console_border_color(&self, color: &COLORREF) -> windows::core::Result<()> {
621
        return unsafe {
622
0
            DwmSetWindowAttribute(
623
0
                GetConsoleWindow(),
624
                DWMWA_BORDER_COLOR,
625
0
                color as *const COLORREF as *const _,
626
0
                mem::size_of::<COLORREF>() as u32,
627
            )
628
        };
629
0
    }
630
631
0
    fn write_console_input(
632
0
        &self,
633
0
        buffer: &[INPUT_RECORD],
634
0
        number_written: &mut u32,
635
0
    ) -> windows::core::Result<()> {
636
        unsafe {
637
0
            windows::Win32::System::Console::WriteConsoleInputW(
638
0
                GetStdHandle(STD_INPUT_HANDLE)?,
639
0
                buffer,
640
0
                number_written,
641
0
            )?
642
        };
643
0
        return Ok(());
644
0
    }
645
646
0
    fn get_last_error(&self) -> u32 {
647
0
        return unsafe { windows::Win32::Foundation::GetLastError().0 };
648
0
    }
649
650
0
    fn generate_console_ctrl_event(
651
0
        &self,
652
0
        ctrl_event: u32,
653
0
        process_group_id: u32,
654
0
    ) -> windows::core::Result<()> {
655
        return unsafe {
656
0
            windows::Win32::System::Console::GenerateConsoleCtrlEvent(ctrl_event, process_group_id)
657
        };
658
0
    }
659
660
0
    fn get_stdout_handle(&self) -> windows::core::Result<HANDLE> {
661
0
        return self.get_std_handle(STD_OUTPUT_HANDLE);
662
0
    }
663
664
0
    fn get_console_attached_process_count(&self) -> u32 {
665
0
        let mut value: [u32; 1] = [0];
666
0
        unsafe { return GetConsoleProcessList(&mut value) };
667
0
    }
668
669
0
    fn get_window_handle_for_process(&self, process_id: u32) -> HWND {
670
        /// Data structure for window search callback
671
        struct WindowSearchData {
672
            /// The process ID we're searching for
673
            target_process_id: u32,
674
            /// Mutable reference to store the found window handle
675
            found_handle: *mut Option<HWND>,
676
        }
677
678
        /// Callback function for finding windows by process ID with proper handle capture
679
0
        unsafe extern "system" fn find_window_callback_with_capture(
680
0
            hwnd: HWND,
681
0
            lparam: LPARAM,
682
0
        ) -> BOOL {
683
0
            let search_data = &mut *(lparam.0 as *mut WindowSearchData);
684
0
            let mut window_process_id: u32 = 0;
685
0
            GetWindowThreadProcessId(hwnd, Some(&mut window_process_id));
686
687
0
            if search_data.target_process_id == window_process_id {
688
                // Store the found window handle
689
0
                *search_data.found_handle = Some(hwnd);
690
0
                return FALSE; // Stop enumeration
691
0
            }
692
0
            return TRUE; // Continue enumeration
693
0
        }
694
695
0
        let mut found_handle = None;
696
0
        let mut search_data = WindowSearchData {
697
0
            target_process_id: process_id,
698
0
            found_handle: &mut found_handle,
699
0
        };
700
701
        loop {
702
0
            let _ = unsafe {
703
0
                EnumWindows(
704
0
                    Some(find_window_callback_with_capture),
705
0
                    LPARAM(&mut search_data as *mut WindowSearchData as isize),
706
0
                )
707
0
            };
708
0
            if let Some(handle) = found_handle {
709
0
                return handle;
710
0
            }
711
        }
712
0
    }
713
714
1
    fn create_process_raw(
715
1
        &self,
716
1
        application: &str,
717
1
        command_line: windows::core::PWSTR,
718
1
        startup_info: &mut windows::Win32::System::Threading::STARTUPINFOW,
719
1
        process_info: &mut windows::Win32::System::Threading::PROCESS_INFORMATION,
720
1
    ) -> windows::core::Result<()> {
721
        return unsafe {
722
1
            CreateProcessW(
723
1
                &HSTRING::from(application),
724
1
                Some(command_line),
725
1
                Some(ptr::null_mut()),
726
1
                Some(ptr::null_mut()),
727
                false,
728
                CREATE_NEW_CONSOLE,
729
1
                Some(ptr::null_mut()),
730
1
                PCWSTR::null(),
731
1
                ptr::addr_of_mut!(*startup_info),
732
1
                ptr::addr_of_mut!(*process_info),
733
            )
734
        };
735
1
    }
736
737
0
    fn get_console_window(&self) -> HWND {
738
0
        return unsafe { GetConsoleWindow() };
739
0
    }
740
741
0
    fn get_foreground_window(&self) -> HWND {
742
0
        return unsafe { GetForegroundWindow() };
743
0
    }
744
745
0
    fn set_foreground_window(&self, hwnd: HWND) -> windows::core::Result<()> {
746
0
        let result = unsafe { SetForegroundWindow(hwnd) };
747
0
        if result.as_bool() {
748
0
            return Ok(());
749
        } else {
750
0
            return Err(windows::core::Error::from_win32());
751
        }
752
0
    }
753
754
0
    fn get_console_mode(&self, handle: HANDLE) -> windows::core::Result<CONSOLE_MODE> {
755
0
        let mut mode = CONSOLE_MODE(0u32);
756
0
        unsafe { GetConsoleMode(handle, &mut mode)? };
757
0
        return Ok(mode);
758
0
    }
759
760
0
    fn set_console_mode(&self, handle: HANDLE, mode: CONSOLE_MODE) -> windows::core::Result<()> {
761
0
        return unsafe { SetConsoleMode(handle, mode) };
762
0
    }
763
764
2
    fn get_exit_code(&self, handle: HANDLE) -> windows::core::Result<u32> {
765
2
        let mut exit_code: u32 = 0;
766
2
        unsafe { GetExitCodeProcess(handle, &mut exit_code)
?0
};
767
2
        return Ok(exit_code);
768
2
    }
769
770
0
    fn move_window(
771
0
        &self,
772
0
        hwnd: HWND,
773
0
        x: i32,
774
0
        y: i32,
775
0
        width: i32,
776
0
        height: i32,
777
0
        repaint: bool,
778
0
    ) -> windows::core::Result<()> {
779
0
        return unsafe { MoveWindow(hwnd, x, y, width, height, repaint) };
780
0
    }
781
782
0
    fn get_window_placement(&self, hwnd: HWND) -> windows::core::Result<WINDOWPLACEMENT> {
783
0
        let mut placement: WINDOWPLACEMENT = WINDOWPLACEMENT {
784
0
            length: mem::size_of::<WINDOWPLACEMENT>() as u32,
785
0
            ..Default::default()
786
0
        };
787
0
        unsafe { GetWindowPlacement(hwnd, &mut placement)? };
788
0
        return Ok(placement);
789
0
    }
790
791
0
    fn show_window(&self, hwnd: HWND, cmd_show: SHOW_WINDOW_CMD) -> windows::core::Result<bool> {
792
0
        let result = unsafe { ShowWindow(hwnd, cmd_show) };
793
0
        return Ok(result.as_bool());
794
0
    }
795
796
0
    fn focus_window_with_automation(&self, hwnd: HWND) -> windows::core::Result<()> {
797
0
        let automation: IUIAutomation =
798
0
            unsafe { CoCreateInstance(&CUIAutomation, None, CLSCTX_ALL)? };
799
0
        let window = unsafe { automation.ElementFromHandle(hwnd)? };
800
0
        unsafe { window.SetFocus()? };
801
0
        return Ok(());
802
0
    }
803
804
0
    fn is_window(&self, hwnd: HWND) -> bool {
805
0
        return unsafe { windows::Win32::UI::WindowsAndMessaging::IsWindow(Some(hwnd)).as_bool() };
806
0
    }
807
808
0
    fn open_process(
809
0
        &self,
810
0
        access: u32,
811
0
        inherit: bool,
812
0
        process_id: u32,
813
0
    ) -> windows::core::Result<HANDLE> {
814
0
        return unsafe { OpenProcess(PROCESS_ACCESS_RIGHTS(access), inherit, process_id) };
815
0
    }
816
817
0
    fn initialize_com_library(
818
0
        &self,
819
0
        coinit: windows::Win32::System::Com::COINIT,
820
0
    ) -> windows::core::Result<()> {
821
0
        let result = unsafe { windows::Win32::System::Com::CoInitializeEx(None, coinit) };
822
0
        if result.is_ok() {
823
0
            return Ok(());
824
        } else {
825
0
            return Err(windows::core::Error::from(result));
826
        }
827
0
    }
828
829
0
    fn get_system_metrics(&self, index: SYSTEM_METRICS_INDEX) -> i32 {
830
0
        return unsafe { windows::Win32::UI::WindowsAndMessaging::GetSystemMetrics(index) };
831
0
    }
832
833
0
    fn set_process_dpi_awareness(
834
0
        &self,
835
0
        value: windows::Win32::UI::HiDpi::PROCESS_DPI_AWARENESS,
836
0
    ) -> windows::core::Result<()> {
837
0
        return unsafe { windows::Win32::UI::HiDpi::SetProcessDpiAwareness(value) };
838
0
    }
839
}
840
841
/// u16 representation of a [KEY_EVENT][1].
842
///
843
/// For some reason the public [KEY_EVENT][1] constant is a u32
844
/// while the [INPUT_RECORD][2].`EventType` is u16...
845
///
846
/// [1]: https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/System/Console/constant.KEY_EVENT.html
847
/// [2]: https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/System/Console/struct.INPUT_RECORD.html
848
pub const KEY_EVENT: u16 = KEY_EVENT_U32 as u16;
849
850
/// Build command line string for Windows process creation
851
///
852
/// # Arguments
853
///
854
/// * `application` - Application name including file extension
855
/// * `args` - List of arguments to the application
856
///
857
/// # Returns
858
///
859
/// UTF-16 encoded command line with proper quoting
860
///
861
/// # Examples
862
///
863
/// ```
864
/// use csshw_lib::utils::windows::build_command_line;
865
///
866
/// let cmd_line = build_command_line("cmd.exe", &["arg1".to_string(), "arg2".to_string()]);
867
/// // Returns UTF-16 encoded: "cmd.exe" "arg1" "arg2"\0
868
/// ```
869
4
pub fn build_command_line(application: &str, args: &[String]) -> Vec<u16> {
870
4
    let mut cmd: Vec<u16> = Vec::new();
871
4
    cmd.push(b'"' as u16);
872
4
    cmd.extend(OsString::from(application).encode_wide());
873
4
    cmd.push(b'"' as u16);
874
875
9
    for 
arg5
in args {
876
5
        cmd.push(' ' as u16);
877
5
        cmd.push(b'"' as u16);
878
5
        cmd.extend(OsString::from(arg).encode_wide());
879
5
        cmd.push(b'"' as u16);
880
5
    }
881
4
    cmd.push(0); // add null terminator
882
883
4
    return cmd;
884
4
}
885
886
/// Sets the back- and foreground color of the current console window using the provided API.
887
///
888
/// # Arguments
889
///
890
/// * `api` - The Windows API implementation to use.
891
/// * `color` - The color value describing the back- and foreground color.
892
///
893
/// # Examples
894
///
895
/// ```no_run
896
/// use csshw_lib::utils::windows::{set_console_color, DefaultWindowsApi};
897
/// use windows::Win32::System::Console::CONSOLE_CHARACTER_ATTRIBUTES;
898
///
899
/// let api = DefaultWindowsApi;
900
/// set_console_color(&api, CONSOLE_CHARACTER_ATTRIBUTES(0x0F));
901
/// ```
902
2
pub fn set_console_color(api: &dyn WindowsApi, color: CONSOLE_CHARACTER_ATTRIBUTES) {
903
2
    api.set_console_text_attribute(color).unwrap();
904
2
    let buffer_info = api.get_console_screen_buffer_info().unwrap();
905
24
    for y in 0..
buffer_info.dwSize.Y2
{
906
24
        api.fill_console_output_attribute(
907
24
            color.0,
908
24
            buffer_info.dwSize.X.try_into().unwrap(),
909
24
            COORD { X: 0, Y: y },
910
24
        )
911
24
        .unwrap();
912
24
    }
913
2
}
914
915
/// Empties the console screen output buffer of the current console window using the provided API.
916
///
917
/// # Arguments
918
///
919
/// * `api` - The Windows API implementation to use.
920
///
921
/// # Examples
922
///
923
/// ```no_run
924
/// use csshw_lib::utils::windows::{clear_screen, DefaultWindowsApi};
925
///
926
/// let api = DefaultWindowsApi;
927
/// clear_screen(&api);
928
/// ```
929
2
pub fn clear_screen(api: &dyn WindowsApi) {
930
2
    let buffer_info = api.get_console_screen_buffer_info().unwrap();
931
2
    let scroll_rect = SMALL_RECT {
932
2
        Left: 0,
933
2
        Top: 0,
934
2
        Right: buffer_info.dwSize.X,
935
2
        Bottom: buffer_info.dwSize.Y,
936
2
    };
937
2
    let scroll_target = COORD {
938
2
        X: buffer_info.dwSize.X,
939
2
        Y: 0 - buffer_info.dwSize.Y,
940
2
    };
941
2
    let mut char_info = CHAR_INFO::default();
942
2
    char_info.Char.UnicodeChar = ' ' as u16;
943
2
    char_info.Attributes = buffer_info.wAttributes.0;
944
945
2
    api.scroll_console_screen_buffer(scroll_rect, scroll_target, char_info)
946
2
        .unwrap();
947
948
2
    let cursor_position = COORD { X: 0, Y: 0 };
949
2
    api.set_console_cursor_position(cursor_position).unwrap();
950
2
}
951
952
/// Sets the border color of the current console window using the provided APIs.
953
///
954
/// Windows10 does not support this.
955
///
956
/// # Arguments
957
///
958
/// * `api` - The Windows API implementation;
959
/// * `color` - RGB [COLORREF][1] to set as border color.
960
///
961
/// # Examples
962
///
963
/// ```no_run
964
/// use csshw_lib::utils::windows::{set_console_border_color, DefaultWindowsApi};
965
/// use windows::Win32::Foundation::COLORREF;
966
///
967
/// set_console_border_color(&DefaultWindowsApi, COLORREF(0x001A2B3C));
968
/// ```
969
///
970
/// [1]: https://learn.microsoft.com/en-us/windows/win32/gdi/colorref
971
3
pub fn set_console_border_color(api: &dyn WindowsApi, color: COLORREF) {
972
3
    if !is_windows_10(api) {
973
2
        api.set_console_border_color(&color).unwrap();
974
2
    
}1
975
3
}
976
977
/// Converts a UTF-16 buffer to a Rust String, filtering out null characters.
978
///
979
/// # Arguments
980
///
981
/// * `buffer` - The UTF-16 buffer to convert.
982
///
983
/// # Returns
984
///
985
/// The converted string.
986
///
987
/// # Examples
988
///
989
/// ```
990
/// use csshw_lib::utils::windows::utf16_buffer_to_string;
991
///
992
/// let utf16_data = vec![72, 101, 108, 108, 111, 0]; // "Hello" + null terminator
993
/// let result = utf16_buffer_to_string(&utf16_data);
994
/// assert_eq!(result, "Hello");
995
/// ```
996
5
pub fn utf16_buffer_to_string(buffer: &[u16]) -> String {
997
5
    let vec: Vec<u16> = buffer
998
5
        .iter()
999
5
        .copied()
1000
49
        .
filter5
(|val| return *val != 0u16)
1001
5
        .collect();
1002
5
    return String::from_utf16(&vec).unwrap_or_else(|err| 
{0
1003
0
        error!("{}", err);
1004
0
        panic!("Failed to convert UTF-16 buffer to string, invalid utf16")
1005
    });
1006
5
}
1007
1008
/// Returns the title of the current console window using the provided API.
1009
///
1010
/// # Arguments
1011
///
1012
/// * `api` - The Windows API implementation to use.
1013
///
1014
/// # Returns
1015
///
1016
/// The title of the current console window.
1017
///
1018
/// # Examples
1019
///
1020
/// ```no_run
1021
/// use csshw_lib::utils::windows::{get_console_title, DefaultWindowsApi};
1022
///
1023
/// let title = get_console_title(&DefaultWindowsApi);
1024
/// println!("Console title: {}", title);
1025
/// ```
1026
0
pub fn get_console_title(api: &dyn WindowsApi) -> String {
1027
0
    let mut title: [u16; MAX_WINDOW_TITLE_LENGTH] = [0; MAX_WINDOW_TITLE_LENGTH];
1028
0
    api.get_console_title(&mut title);
1029
0
    return utf16_buffer_to_string(&title);
1030
0
}
1031
1032
/// Returns a [HANDLE] to the requested [STD_HANDLE] of the current process.
1033
///
1034
/// # Arguments
1035
///
1036
/// * `nstdhandle` - The standard handle to retrieve.
1037
///                  Either [STD_INPUT_HANDLE] or [STD_OUTPUT_HANDLE].
1038
///
1039
/// # Returns
1040
///
1041
/// The [HANDLE] to the requested [STD_HANDLE].
1042
0
fn get_std_handle(nstdhandle: STD_HANDLE) -> HANDLE {
1043
    return unsafe {
1044
0
        GetStdHandle(nstdhandle)
1045
0
            .unwrap_or_else(|_| panic!("Failed to retrieve standard handle: {nstdhandle:?}"))
1046
    };
1047
0
}
1048
1049
/// Returns a [HANDLE] to the [STD_INPUT_HANDLE] of the current process.
1050
///
1051
/// # Returns
1052
///
1053
/// Handle to the standard input of the current process.
1054
///
1055
/// # Examples
1056
///
1057
/// ```no_run
1058
/// use csshw_lib::utils::windows::get_console_input_buffer;
1059
///
1060
/// let input_handle = get_console_input_buffer();
1061
/// ```
1062
0
pub fn get_console_input_buffer() -> HANDLE {
1063
0
    return get_std_handle(STD_INPUT_HANDLE);
1064
0
}
1065
1066
/// Returns a [HANDLE] to the [STD_OUTPUT_HANDLE] of the current process.
1067
///
1068
/// # Returns
1069
///
1070
/// Handle to the standard output of the current process.
1071
///
1072
/// # Examples
1073
///
1074
/// ```no_run
1075
/// use csshw_lib::utils::windows::get_console_output_buffer;
1076
///
1077
/// let output_handle = get_console_output_buffer();
1078
/// ```
1079
0
pub fn get_console_output_buffer() -> HANDLE {
1080
0
    return get_std_handle(STD_OUTPUT_HANDLE);
1081
0
}
1082
1083
/// Returns a single [INPUT_RECORD] read from the current process stdinput using the provided API.
1084
///
1085
/// Blocks until 1 record was read.
1086
///
1087
/// # Arguments
1088
///
1089
/// * `api` - The Windows API implementation to use.
1090
///
1091
/// # Returns
1092
///
1093
/// A single INPUT_RECORD that was read.
1094
///
1095
/// # Examples
1096
///
1097
/// ```no_run
1098
/// use csshw_lib::utils::windows::{read_console_input, DefaultWindowsApi};
1099
///
1100
/// let api = DefaultWindowsApi;
1101
/// let input_record = read_console_input(&api);
1102
/// ```
1103
5
pub fn read_console_input(api: &dyn WindowsApi) -> INPUT_RECORD {
1104
    const NB_EVENTS: usize = 1;
1105
5
    let mut input_buffer: [INPUT_RECORD; NB_EVENTS] = [INPUT_RECORD::default(); NB_EVENTS];
1106
    loop {
1107
6
        let number_of_events_read = api
1108
6
            .read_console_input(&mut input_buffer)
1109
6
            .expect("Failed to read console input");
1110
6
        if number_of_events_read == NB_EVENTS as u32 {
1111
5
            break;
1112
1
        }
1113
    }
1114
5
    return input_buffer[0];
1115
5
}
1116
1117
/// Returns a single [INPUT_RECORD_0] where `EventType` == [`KEY_EVENT`] using the provided API.
1118
///
1119
/// Blocks until 1 key event record was read.
1120
///
1121
/// # Arguments
1122
///
1123
/// * `api` - The Windows API implementation to use.
1124
///
1125
/// # Returns
1126
///
1127
/// A single INPUT_RECORD_0 with EventType == KEY_EVENT.
1128
///
1129
/// # Examples
1130
///
1131
/// ```no_run
1132
/// use csshw_lib::utils::windows::{read_keyboard_input, DefaultWindowsApi};
1133
///
1134
/// let api = DefaultWindowsApi;
1135
/// let key_event = read_keyboard_input(&api);
1136
/// ```
1137
1
pub fn read_keyboard_input(api: &dyn WindowsApi) -> INPUT_RECORD_0 {
1138
    loop {
1139
2
        let input_record = read_console_input(api);
1140
2
        match input_record.EventType {
1141
            KEY_EVENT => {
1142
1
                return input_record.Event;
1143
            }
1144
            _ => {
1145
1
                continue;
1146
            }
1147
        }
1148
    }
1149
1
}
1150
1151
/// Changes size and position of the current console window using the provided API.
1152
///
1153
/// # Arguments
1154
///
1155
/// * `api` - The Windows API implementation to use.
1156
/// * `x`       - The x coordinate to move the window to.
1157
///               From the top left corner of the screen.
1158
/// * `y`       - The y coordinate to move the window to.
1159
///               From the top left corner of the screen.
1160
/// * `width`   - The width in pixels to resize the window to.
1161
///               In logical scaling.
1162
/// * `height`  - The height in pixels to resize the window to.
1163
///               In logical scaling.
1164
///
1165
/// # Examples
1166
///
1167
/// ```no_run
1168
/// use csshw_lib::utils::windows::{arrange_console, DefaultWindowsApi};
1169
///
1170
/// let api = DefaultWindowsApi;
1171
/// arrange_console(&api, 100, 100, 800, 600);
1172
/// ```
1173
0
pub fn arrange_console(api: &dyn WindowsApi, x: i32, y: i32, width: i32, height: i32) {
1174
    // FIXME: sometimes a daemon or client console isn't being arrange correctly
1175
    // when this simply retrying doesn't solve the issue. Maybe it has something to do
1176
    // with DPI awareness => https://docs.rs/embed-manifest/latest/embed_manifest/
1177
0
    api.arrange_console(x, y, width, height).unwrap();
1178
0
}
1179
1180
/// Detects if the current windows installation is Windows 10 or not using the provided API.
1181
///
1182
/// Uses the os version, Windows 10 is < `10._.22000`. Windows 11 started with build 22000.
1183
///
1184
/// # Arguments
1185
///
1186
/// * `api` - The Windows API implementation to use.
1187
///
1188
/// # Returns
1189
///
1190
/// Whether the current windows installation is Windows 10 or not.
1191
///
1192
/// # Examples
1193
///
1194
/// ```no_run
1195
/// use csshw_lib::utils::windows::{is_windows_10, DefaultWindowsApi};
1196
///
1197
/// if is_windows_10(&DefaultWindowsApi) {
1198
///     println!("Running on Windows 10");
1199
/// } else {
1200
///     println!("Running on Windows 11 or newer");
1201
/// }
1202
/// ```
1203
10
pub fn is_windows_10(api: &dyn WindowsApi) -> bool {
1204
10
    let version = api.get_os_version();
1205
10
    let mut iter = version.split('.');
1206
10
    let (major, _, build) = (
1207
10
        iter.next().unwrap().parse::<usize>().unwrap(),
1208
10
        iter.next().unwrap().parse::<usize>().unwrap(),
1209
10
        iter.next().unwrap().parse::<usize>().unwrap(),
1210
10
    );
1211
10
    return major < 10 || (
major == 109
&&
build < 220007
);
1212
10
}
1213
1214
#[cfg(test)]
1215
#[path = "../tests/utils/test_windows.rs"]
1216
mod test_mod;